# -*- coding: utf-8 -*-

# Podboy -- A podcast aggregator/player
#
# Copyright (C) 2009-2012 Valéry Febvre <vfebvre@easter-eggs.com>
# http://code.google.com/p/podboy/
#
# This file is part of Podboy.
#
# Podboy is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# Podboy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os, time
from datetime import datetime
import ecore, evas, elementary

from const import *
from database import db
from libpodcast import Podcast, PodcastEpisode
from libplayer import gst, PodboyPlayer, MediaTypes
import util

__all__ = [
    'PodcastsPage', 'DownloadsPage', 'EpisodesPage', 'PlayerPage', 'SettingsPage', 'AboutPage',
    'ConfirmDialog', 'FileselectorDialog', 'InfoDialog', 'NotifyDialog'
    ]


#
# PodcastsPageCommon class herited by:
#   PodcastsPage
#   DownloadsPage
#   EpisodesPage
#

class PodcastsPageCommon(object):
    def __init__(self, main):
        self.main = main
        self.view = None
        self.current_podcast = None
        self.box = None
        self.is_built = False

    def build(self):
        self.is_built = True

        self.box = elementary.Box(self.main.win)
        self.box.size_hint_weight_set(1, 1)
        self.box.size_hint_align_set(-1, -1)
        self.box.show()

        self.title = elementary.Label(self.main.win)
        self.box.pack_end(self.title)
        self.title.text_set('')
        self.title.show()

        self.list = elementary.List(self.main.win)
        self.list.multi_select_set(db.get_setting('gui_list_multi_select') or False)
        self.list.size_hint_weight_set(1, 1)
        self.list.size_hint_align_set(-1, -1)
        self.box.pack_end(self.list)
        self.list.show()
        self.list.callback_clicked_add(self.on_list_item_clicked)
        self.list_items_icons = None

        self.status = elementary.Label(self.main.win)
        self.status.size_hint_weight_set(0, 0)
        self.status.size_hint_align_set(0.5, 0.5)
        self.status.scale_set(0.75)
        self.box.pack_end(self.status)
        self.clear_status()
        self.status_timer = None

        self.box_actions = elementary.Box(self.main.win)
        self.box_actions.horizontal_set(True)
        self.box_actions.homogenous_set(True)
        self.box_actions.size_hint_weight_set(1, 0)
        self.box_actions.size_hint_align_set(-1, 0)
        self.box.pack_end(self.box_actions)
        self.box_actions.show()

        self.btns_actions_podcasts = []
        self.btns_actions_episodes = []

        self.build_next()

    def add_list_item(self, podcast, nb_episodes = None):
        icon_size = elementary.config_finger_size_get() + 5

        layout = elementary.Layout(self.main.win)
        layout.size_hint_min_set(icon_size, icon_size)
        layout.file_set(os.path.join(DATA_DIR, 'podboy.edj'), 'list-episode-cover-status')
        # cover
        cover = elementary.Icon(self.main.win)
        if podcast.cover_full_path and os.path.exists(podcast.cover_full_path):
            try:
                cover.file_set(podcast.cover_full_path)
            except evas.c_evas.EvasLoadError, e:
                cover.file_set(THEME_EDJ, 'podboy/podcast')
        else:
            cover.file_set(THEME_EDJ, 'podboy/podcast')
        cover.size_hint_min_set(icon_size, icon_size)
        layout.content_set('cover', cover)
        # status (text)
        status_1 = None
        if self.__class__.__name__ in ('DownloadsPage', 'EpisodesPage'):
            status_1 = evas.Text(self.main.win.evas)
            status_1.style_set(evas.EVAS_TEXT_STYLE_SOFT_OUTLINE)
            status_1.color = (0, 0, 0, 255)
            status_1.outline_color = (255, 255, 255, 255)
            status_1.font_set('LiberationMono-Regular', 14)
            if self.__class__.__name__ == 'EpisodesPage':
                # display number of unplayed episodes / total number of episodes
                status_1.text_set(
                    '%d/%d' % (db.count_episodes(podcast_id = podcast.id, downloaded = 1, played = 0), nb_episodes)
                    )
            elif self.__class__.__name__ == 'DownloadsPage':
                # display number of episodes available to download
                status_1.text_set(str(nb_episodes))
            layout.content_set('status-t1', status_1)

        self.list.item_append(podcast.title.encode('utf-8'), layout, None, None, podcast.id)
        self.list_items_icons[podcast.id] = {
            'cover': None,
            'status-1': status_1,
            'status-2': None,
            }

    def check_media_dir(self):
        if not db.get_setting('media_dir'):
            self.set_status('No folder defined for media')
            return False
        elif not os.path.exists(db.get_setting('media_dir').encode('utf-8')):
            self.set_status("Media folder doesn't exist")
            return False
        else:
            return True

    def check_selected(self, multi_allowed = False):
        nb = len(self.list.selected_items_get())
        if nb == 0:
            if self.view == 'podcasts':
                self.set_status('Please select a podcast')
            else:
                self.set_status('Please select an episode')
            return False
        elif nb > 1 and not multi_allowed:
            if self.view == 'podcasts':
                self.set_status('Too many podcasts selected')
            else:
                self.set_status('Too many episodes selected')
            return False
        return True

    def clear_selected(self):
        for item in self.list.selected_items_get():
            item.selected = False

    def clear_status(self):
        self.status.text_set('')
        # is this a hack ? required to hide label
        self.status.size_hint_min_set(0, 0)
        self.status.hide()

    def on_list_item_clicked(self, lst, item):
        if self.view == 'podcasts' and self.__class__.__name__ != 'PodcastsPage':
            self.toggle_view(podcast_id = item.data[0][0])
        elif self.view == 'episodes' and self.__class__.__name__ == 'EpisodesPage':
            self.play(episode_id = item.data[0][0])

    def populate_episodes(self):
        self.list.clear()
        self.list.mode = elementary.ELM_LIST_SCROLL
        self.list_items_icons = {}

        if self.__class__.__name__ == 'DownloadsPage':
            episodes = db.get_episodes(podcast_id = self.current_podcast.id, downloaded = 0)
        if self.__class__.__name__ == 'EpisodesPage':
            episodes = db.get_episodes(podcast_id = self.current_podcast.id, downloaded = 1)
        for episode in episodes:
            # title
            title = "[%s] %s%s" % (
                datetime.fromtimestamp(episode['updated']).strftime("%Y.%m.%d"),
                episode['title'].encode('utf-8'),
                ' (%s)' % episode['duration'].encode('utf-8') if episode['duration'] else '',
                )

            icon_size = elementary.config_finger_size_get() + 5

            layout = elementary.Layout(self.main.win)
            layout.size_hint_min_set(icon_size, icon_size)
            layout.file_set(os.path.join(DATA_DIR, 'podboy.edj'), 'list-episode-cover-status')
            # cover
            cover = elementary.Icon(self.main.win)
            if self.current_podcast.cover_full_path and os.path.exists(self.current_podcast.cover_full_path):
                try:
                    cover.file_set(self.current_podcast.cover_full_path)
                except evas.c_evas.EvasLoadError, e:
                    cover.file_set(THEME_EDJ, "podboy/podcast")
            else:
                cover.file_set(THEME_EDJ, "podboy/podcast")
            cover.size_hint_min_set(icon_size, icon_size)
            layout.content_set('cover', cover)
            # status 1 (icon)
            status_1 = elementary.Icon(self.main.win)
            if episode['downloaded'] == -1 and self.__class__.__name__ == 'DownloadsPage':
                status_1.file_set(THEME_EDJ, "podboy/status-ignore")
            elif episode['played'] == 0 and self.__class__.__name__ == 'EpisodesPage':
                status_1.file_set(THEME_EDJ, "podboy/status-unplayed")
            layout.content_set('status-i1', status_1)
            # status 2 (icon)
            status_2 = elementary.Icon(self.main.win)
            if episode['locked'] == 1 and self.__class__.__name__ == 'EpisodesPage':
                status_2.file_set(THEME_EDJ, "podboy/status-locked")
            layout.content_set('status-i2', status_2)

            self.list.item_append(title, layout, None, None, episode['id'])
            self.list_items_icons[episode['id']] = {
                'cover': None,
                'status-1': status_1,
                'status-2': status_2,
                }
        self.list.go()

        self.set_title()

    def populate_podcasts(self):
        self.list.clear()
        self.list.mode = elementary.ELM_LIST_COMPRESS
        self.list_items_icons = {}

        counter = 0
        for podcast in db.get_podcasts():
            nb_episodes = None
            if self.__class__.__name__ == 'DownloadsPage':
                nb_episodes = db.count_episodes(podcast_id = podcast['id'], downloaded = 0)
            elif self.__class__.__name__ == 'EpisodesPage':
                nb_episodes = db.count_episodes(podcast_id = podcast['id'], downloaded = 1)
                if nb_episodes == 0:
                    continue
            self.add_list_item(Podcast.load(data = podcast), nb_episodes)
            counter += 1
        self.list.go()

        self.set_title(nb_podcasts = counter)

    def promote(self):
        if not self.is_built:
            self.build()
        self.main.pager.item_simple_promote(self.box)

    @property
    def selected_ids(self):
        return [item.data[0][0] for item in self.list.selected_items_get()]

    def set_status(self, message, timeout = STATUS_MSG_TIMEOUT):
        """Displays a status message at the bottom of list widget

        Args:
           message (str): The message
           timeout (float): Number of seconds beyond which the message disappears
        """
        self.status.text_set('<b>%s</>' % message)
        self.status.show()
        self.main.win.evas_get().render()
        if timeout is not None and timeout > 0:
            if self.status_timer:
                self.status_timer.delete()
            self.status_timer = ecore.timer_add(timeout, self.clear_status)

    def set_title(self, nb_podcasts = None, nb_episodes = None):
        """Displays a title at the top of list widget

        In view "podcasts", the total number of podcats and episodes is displayed.
        In view "episodes", the total number of episodes of the current podcast is displayed.

        Args:
           nb_podcasts (int): The total number of podcasts (optional)
           nb_episodes (int): The total number of episodes (optional)
        """
        msgs = []
        if self.view == 'podcasts':
            if nb_podcasts is None:
                nb_podcasts = db.count_podcasts()
            if nb_podcasts == 0:
                msg = 'No podcast'
            elif nb_podcasts == 1:
                msg = '1 podcast'
            else:
                msg = '%d podcasts' % nb_podcasts
            msgs.append(msg)
            self.nb_podcasts = nb_podcasts

        if self.__class__.__name__ != 'PodcastsPage':
            if nb_episodes is None:
                if self.__class__.__name__ == 'DownloadsPage':
                    downloaded = 0
                if self.__class__.__name__ == 'EpisodesPage':
                    downloaded = 1
                if self.view == 'episodes' and self.current_podcast.id:
                    nb_episodes = db.count_episodes(podcast_id = self.current_podcast.id, downloaded = downloaded)
                else:
                    nb_episodes = db.count_episodes(downloaded = downloaded)

            if nb_episodes == 0:
                msg = 'No episode'
            elif nb_episodes == 1:
                msg = '1 episode'
            else:
                msg = '%d episodes' % nb_episodes
            msgs.append(msg)

        self.title.text_set('<b>%s</b>' % ' / '.join(msgs))

    def show_episode_details(self, btn):
        if not self.check_selected():
            return

        episode = PodcastEpisode.load(self.selected_ids[0])
        details = "<b>%s</><br><br>" % episode.title
        details += "%s | %s | %s<br><br>" % (
            datetime.fromtimestamp(episode.updated).strftime("%Y.%m.%d"),
            episode.duration if episode.duration else 'Unknown',
            util.format_filesize(episode.length),
            )
        details += episode.description
        self.main.info_dlg.open(details.encode('utf-8').replace('&', '&amp;'))

    def toggle_view(self, btn = None, podcast_id = None):
        """Allows to toggle between the view "podcasts" and the view "episodes"

        Args:
           podcast_id (int): The Id of a podcast (only required to toggle to view "episodes")
        """
        if self.view == 'podcasts':
            if podcast_id is None and not self.check_selected():
                return
            podcast_id = podcast_id if podcast_id else self.selected_ids[0]

            for btn in self.btns_actions_podcasts:
                btn.hide()
                self.box_actions.unpack(btn)
            for btn in self.btns_actions_episodes:
                self.box_actions.pack_end(btn)
                btn.show()

            self.view = 'episodes'
            self.current_podcast = Podcast.load(id = podcast_id)
            self.populate_episodes()

        elif self.view is None or self.view == 'episodes':
            if self.view is None:
                self.main.pager.item_simple_push(self.box)

            for btn in self.btns_actions_episodes:
                self.box_actions.unpack(btn)
                btn.hide()
            for btn in self.btns_actions_podcasts:
                self.box_actions.pack_end(btn)
                btn.show()

            self.view = 'podcasts'
            self.current_podcast = None
            self.populate_podcasts()


#
# Podcasts page
#

class PodcastsPage(PodcastsPageCommon):
    def __init__(self, main):
        PodcastsPageCommon.__init__(self, main)

    def build_next(self):
        # subscription box (button + input)
        box_add = elementary.Box(self.main.win)
        box_add.horizontal_set(True)
        box_add.size_hint_align_set(-1, -1)
        box_add.show()

        btn_add = elementary.Button(self.main.win)
        btn_add.text_set("Add")
        box_add.pack_start(btn_add)
        btn_add.callback_clicked_add(self.add)
        btn_add.show()

        scroller_add = elementary.Scroller(self.main.win)
        scroller_add.bounce_set(False, False)
        scroller_add.size_hint_weight_set(1, 1)
        scroller_add.size_hint_align_set(-1, -1)
        scroller_add.show()

        self.input_add = elementary.Entry(self.main.win)
        self.input_add.single_line_set(True)
        self.input_add.entry_set('http://')
        self.input_add.size_hint_weight_set(1, 1)
        self.input_add.callback_activated_add(self.add)
        scroller_add.content_set(self.input_add)
        self.input_add.show()

        box_add.pack_end(scroller_add)
        self.box.pack_start(box_add)

        # "more actions" hover
        self.hover_more_actions = Hover(self, 'Actions', 'podcasts')
        self.hover_more_actions.add_button('Edit', self.edit)
        self.hover_more_actions.add_button('Delete', self.confirm_delete)

        # import OPML button
        btn = elementary.Button(self.main.win)
        btn.text_set("Import")
        btn.size_hint_weight_set(1, 1)
        btn.size_hint_align_set(-1, -1)
        btn.callback_clicked_add(self.select_opml)
        self.btns_actions_podcasts.append(btn)
        btn.show()

        self.toggle_view()

    def add(self, btn = None, feeds = None):
        if not self.check_media_dir():
            return

        if btn and feeds is None:
            url = self.input_add.entry_get().strip()
            url = url.replace('<br>', '')
            url = url.replace('&amp;', '&')
            feeds = [{'title': None, 'url': url}]

        if len(feeds) == 1:
            self.set_status('Retrieving podcast info ...', None)
        else:
            self.set_status('Retrieving podcasts info ...', None)

        successes = []
        errors = []
        for feed in feeds:
            url = feed['url']
            if url == '':
                continue
            if not url.startswith(('http://', 'itpc://', 'feed://', 'itms://')):
                if feed['title']:
                    errors.append('Invalid URL for podcast "%s"' % feed['title'])
                else:
                    errors.append('Invalid URL')
                continue
            podcast = Podcast(url)
            if podcast.error:
                if feed['title']:
                    errors.append('%s "%s".' % (podcast.error[1], feed['title']))
                else:
                    errors.append(podcast.error[1])
            else:
                successes.append(feed['title'])
                self.add_list_item(podcast)

        if successes:
            self.list.go()
            if btn:
                self.input_add.entry_set('http://')
            self.set_title(nb_podcasts = self.nb_podcasts + len(successes))
            self.main.downloads.refresh(event = 'podcast-add')

        self.clear_status()
        if errors or len(successes) > 1:
            if len(successes) == 0:
                msg = 'No podcast added'
            else:
                msg = '%d podcast(s) added: %s' % (len(successes), ', '.join(successes))
            if errors:
                msg += '<br><br><b>Errors:</><br>' + '<br><br>'.join(errors)
            self.main.info_dlg.open(msg)
        else:
            self.main.notify_dlg.open('Podcast successfully added', None)

    def confirm_delete(self, *args):
        if not self.check_selected(multi_allowed = True):
            return

        if len(self.selected_ids) == 1:
            podcast = Podcast.load(self.selected_ids[0])
            self.main.confirm_dlg.open(
                'Do you really want to delete "%s"?' % podcast.title.encode('utf-8'), self.delete
                )
        else:
            self.main.confirm_dlg.open(
                'Do you really want to delete these %d episodes?' % len(self.selected_ids), self.delete
                )

    def delete(self):
        for item in self.list.selected_items_get():
            id = item.data[0][0]
            podcast = Podcast.load(id)
            podcast.delete()
            item.delete()
            self.set_title(nb_podcasts = self.nb_podcasts - 1)
            self.main.downloads.refresh(event = 'podcast-delete', podcast_id = id)
            self.main.episodes.refresh(event = 'podcast-delete', podcast_id = id)

    def edit(self, *args):
        if not self.check_selected():
            return
        podcast = Podcast.load(self.selected_ids[0])

        iwin = elementary.InnerWindow(self.main.win)
        iwin.style_set("minimal_vertical")

        box = elementary.Box(iwin)
        iwin.content_set(box)
        box.show()

        #
        # URL
        #
        frame = elementary.Frame(iwin)
        frame.text_set('Source URL')
        frame.size_hint_align_set(-1, -1)
        box.pack_start(frame)
        frame.show()

        box_link = elementary.Box(iwin)
        box_link.horizontal_set(True)
        frame.content_set(box_link)
        box_link.show()

        btn_fake = elementary.Button(iwin)
        # hide fake button (set width 0)
        btn_fake.size_hint_min_set(0, btn_fake.size_hint_min[1])
        box_link.pack_end(btn_fake)
        btn_fake.show()

        scroller = elementary.Scroller(iwin)
        scroller.bounce_set(False, False)
        scroller.size_hint_weight_set(1, 1)
        scroller.size_hint_align_set(-1, -1)
        box_link.pack_end(scroller)
        scroller.show()

        entry_link = elementary.Entry(iwin)
        entry_link.single_line_set(True)
        entry_link.entry_set(podcast.link)
        entry_link.size_hint_weight_set(1, 1)
        scroller.content_set(entry_link)
        entry_link.show()

        #
        # Title
        #
        frame = elementary.Frame(iwin)
        frame.text_set('Title')
        frame.size_hint_align_set(-1, -1)
        box.pack_start(frame)
        frame.show()

        box_title = elementary.Box(iwin)
        box_title.horizontal_set(True)
        frame.content_set(box_title)
        box_title.show()

        btn_fake = elementary.Button(iwin)
        # hide fake button (set width 0)
        btn_fake.size_hint_min_set(0, btn_fake.size_hint_min[1])
        box_title.pack_end(btn_fake)
        btn_fake.show()

        scroller = elementary.Scroller(iwin)
        scroller.bounce_set(False, False)
        scroller.size_hint_weight_set(1, 1)
        scroller.size_hint_align_set(-1, -1)
        box_title.pack_end(scroller)
        scroller.show()

        entry_title = elementary.Entry(iwin)
        entry_title.single_line_set(True)
        entry_title.entry_set(podcast.title.encode('utf-8'))
        entry_title.size_hint_weight_set(1, 1)
        scroller.content_set(entry_title)
        entry_title.show()

        box_actions = elementary.Box(iwin)
        box_actions.horizontal_set(True)
        box_actions.homogenous_set(True)
        box_actions.size_hint_align_set(-1, -1)
        box.pack_end(box_actions)
        box_actions.show()

        def close(*args):
            iwin.delete()
        btn_cancel = elementary.Button(iwin)
        btn_cancel.text_set("Cancel")
        btn_cancel.size_hint_weight_set(1, 1)
        btn_cancel.size_hint_align_set(-1, -1)
        btn_cancel.callback_clicked_add(close)
        box_actions.pack_end(btn_cancel)
        btn_cancel.show()

        def update(*args):
            self.set_status('Update podcast info ...', None)
            res = podcast.update(entry_title.entry_get().strip(), entry_link.entry_get().strip())
            if res is True:
                close()
                self.set_status('Podcast info were updated')
                self.populate_podcasts()
                self.main.downloads.refresh(event = 'podcast-update')
                self.main.episodes.refresh(event = 'podcast-update')
            else:
                msg = 'Update of the podcast "%s" has failed.<br><br><b>Errors:</>' % podcast.title.encode('utf-8')
                for error in res:
                    msg += '<br><br>%s' % error
                self.main.info_dlg.open(msg)
                self.clear_status()
        btn_ok = elementary.Button(iwin)
        btn_ok.text_set("OK")
        btn_ok.size_hint_weight_set(1, 1)
        btn_ok.size_hint_align_set(-1, -1)
        btn_ok.callback_clicked_add(update)
        box_actions.pack_end(btn_ok)
        btn_ok.show()

        iwin.show()

    def import_opml(self, path):
        if not path:
            return

        from xml.dom.minidom import parse
        try:
            dom = parse(path)
        except:
            self.set_status('Failed to parse OPML file')
            return

        feeds = []
        for link in dom.getElementsByTagName('outline'):
            title = link.getAttribute('title') or ''
            url = link.getAttribute('xmlUrl')
            if url:
                feeds.append({'title': title.encode('utf-8'), 'url': url.strip()})
        if feeds:
            self.add(feeds = feeds)
        else:
            self.set_status('No podcast found in OPML file')

    def select_opml(self, *args):
        self.main.fs_dlg.open('Import OPML file', self.import_opml)


#
# New episodes (not yet downloaded) page
#

class DownloadsPage(PodcastsPageCommon):
    def __init__(self, main):
        PodcastsPageCommon.__init__(self, main)
        self.dl_timer = None

    def build_next(self):
        # "view episodes of selected podcast" button
        btn = elementary.Button(self.main.win)
        btn.text_set("Episodes")
        btn.size_hint_weight_set(1, 1)
        btn.size_hint_align_set(-1, -1)
        btn.callback_clicked_add(self.toggle_view)
        self.btns_actions_podcasts.append(btn)
        btn.show()

        # "more actions" hover
        self.hover_more_actions = Hover(self, 'Actions', 'podcasts')
        self.hover_more_actions.add_button('Check All For Updates', self.check_updates, False)
        self.hover_more_actions.add_button('Check Selected For Updates', self.check_updates, True)

        # downloads hover
        self.hover_downloads = Hover(self, 'Download', 'podcasts')
        self.hover_downloads.add_button('All', self.confirm_download, False)
        self.hover_downloads.add_button('Selected', self.confirm_download, True)
        self.hover_downloads.add_button('Cancel', self.confirm_cancel_download)
        self.hover_downloads.hide_button(2)

        # "back to podcasts" button
        btn = elementary.Button(self.main.win)
        btn.text_set("Back")
        btn.size_hint_weight_set(1, 1)
        btn.size_hint_align_set(-1, -1)
        btn.callback_clicked_add(self.toggle_view)
        self.btns_actions_episodes.append(btn)
        btn.show()

        # "more actions" hover
        self.hover_more_actions_episodes = Hover(self, 'Actions', 'episodes')
        self.hover_more_actions_episodes.add_button('Show Details', self.show_episode_details)
        self.hover_more_actions_episodes.add_button('Toggle Ignore Status', self.toggle_ignore_status)
        self.hover_more_actions_episodes.add_button('Check For Updates', self.check_updates, False)

        # downloads hover
        self.hover_downloads_episodes = Hover(self, 'Download', 'episodes')
        self.hover_downloads_episodes.add_button('All', self.confirm_download, False)
        self.hover_downloads_episodes.add_button('Selected', self.confirm_download, True)
        self.hover_downloads_episodes.add_button('Cancel', self.confirm_cancel_download)
        self.hover_downloads_episodes.hide_button(2)

        self.toggle_view()

    def cancel_download(self):
        self.dl_timer.delete()
        self.dl_timer = None

        for i, exe in enumerate(self.dl_exes):
            try:
                exe.terminate()
            except:
                pass
            episode = self.dl_queue[i]
            episode.set_undownloaded()

        self.set_status('Download canceled')

        self.hover_downloads.enable_button(0)
        self.hover_downloads.enable_button(1)
        self.hover_downloads.hide_button(2)
        self.hover_downloads_episodes.enable_button(0)
        self.hover_downloads_episodes.enable_button(1)
        self.hover_downloads_episodes.hide_button(2)

        if self.view == 'episodes':
            self.populate_episodes()
        else:
            self.set_title()

    def check_updates(self, btn, selected_items_only = False):
        self.set_status('Checking for new episodes ...', None)

        podcasts = []
        if self.view == 'episodes':
            podcasts.append(self.current_podcast)
        else:
            if selected_items_only:
                if not self.check_selected(multi_allowed = True):
                    return
                for podcast_id in self.selected_ids:
                    podcasts.append(Podcast.load(podcast_id))
            else:
                for podcast in db.get_podcasts():
                    podcasts.append(Podcast.load(podcast['id']))

        successes = []
        errors = []
        for podcast in podcasts:
            counter = podcast.check_updates()
            if counter and sum(counter) > 0:
                successes.append((podcast.title.encode('utf-8'), counter))
            elif podcast.error:
                errors.append((podcast.title.encode('utf-8'), podcast.error[1]))

        if len(successes):
            self.refresh()

        self.clear_status()
        if len(podcasts) == 1 or (not successes and not errors):
            if errors:
                msg = errors[0][1].split(' (')[0]
            else:
                counter = successes[0][1][0] if successes else 0
                if counter == 0:
                    msg = 'No new episode available'
                elif counter == 1:
                    msg = '1 new episode available'
                else:
                    msg = '%d new episodes available' % counter
            self.main.notify_dlg.open(msg, None)
        else:
            msg = ''
            if successes:
                msg += '<b>New episodes:</><br>'
                for success in successes:
                    counter = success[1][0]
                    if counter == 1:
                        msg += '* %s: 1 episode<br>' % success[0]
                    elif counter > 1:
                        msg += '* %s: %d episodes<br>' % (success[0], counter)
                msg += '<br>'
            if errors:
                msg += '<b>Errors:</><br>'
                for error in errors:
                    msg += '* %s: %s<br>' % error
            self.main.info_dlg.open(msg)

    def confirm_cancel_download(self, btn):
        if self.dl_timer is None:
            return

        self.main.confirm_dlg.open('Do you really want to cancel the download?', self.cancel_download)

    def confirm_download(self, btn, selected_items_only):
        if not self.check_media_dir():
            return

        if selected_items_only and not self.check_selected(multi_allowed = True):
            return

        msg = None
        if self.view == 'podcasts':
            if selected_items_only:
                if len(self.selected_ids) == 1:
                    podcast = Podcast.load(self.selected_ids[0])
                    msg = 'Do you really want to download all episodes of "%s"?' % podcast.title.encode('utf-8')
                else:
                    msg = 'Do you really want to download all episodes of these %d podcasts?' % len(self.selected_ids)
            else:
                msg = "Do you really want to download all episodes of all podcasts?"
        elif self.view == 'episodes' and not selected_items_only:
            msg = 'Do you really want to download all episodes of "%s"?' % self.current_podcast.title.encode('utf-8')

        if msg:
            self.main.confirm_dlg.open(msg, self.download, selected_items_only = selected_items_only)
        else:
            self.download(selected_items_only = selected_items_only)

    def download(self, btn = None, event = None, selected_items_only = True):
        if self.dl_timer:
            return
        self.clear_status()

        self.dl_queue_pos = 0
        self.dl_queue = []
        self.dl_block = []
        self.dl_exes = []
        self.dl_errors = []
        if self.view == 'podcasts':
            if selected_items_only:
                for podcast_id in self.selected_ids:
                    for ep in db.get_episodes(podcast_id = podcast_id, downloaded = 0):
                        episode = PodcastEpisode.load(ep['id'])
                        if episode.downloaded == 0:
                            self.dl_queue.append(episode)
            else:
                for ep in db.get_episodes(downloaded = 0):
                    episode = PodcastEpisode.load(ep['id'])
                    if episode.downloaded == 0:
                        self.dl_queue.append(episode)

        elif self.view == 'episodes' and self.current_podcast:
            if selected_items_only:
                for episode_id in self.selected_ids:
                    episode = PodcastEpisode.load(episode_id)
                    self.dl_queue.append(episode)
            else:
                for ep in db.get_episodes(podcast_id = self.current_podcast.id, downloaded = 0):
                    episode = PodcastEpisode.load(ep['id'])
                    if episode.downloaded == 0:
                        self.dl_queue.append(episode)

        for episode in self.dl_queue[:]:
            if not episode.download(check_only = True):
                self.dl_queue.remove(episode)
                self.set_status('Failed to download an episode')

        if len(self.dl_queue):
            self.hover_downloads.disable_button(0)
            self.hover_downloads.disable_button(1)
            self.hover_downloads.show_button(2)
            self.hover_downloads_episodes.disable_button(0)
            self.hover_downloads_episodes.disable_button(1)
            self.hover_downloads_episodes.show_button(2)

            self.dl_timer = ecore.timer_add(1, self.process_download)
        else:
            self.set_status('Nothing to download')

    def on_download_terminated(self, exe, event):
        if event.signalled:
            # occurs when download is cancelled by user
            return

        episode = self.dl_queue[self.dl_exes.index(exe)]
        if event.exit_code == 0:
            episode.set_downloaded()
            self.refresh()
            self.main.episodes.refresh(event = 'episode-add', podcast_id = episode.podcast_id)
        else:
            self.dl_errors.append(episode)
        self.dl_block.remove(episode)

    def process_download(self):
        counter        = 0
        downloads_size = 0
        total_size     = 0

        if len(self.dl_block) == 0:
            self.dl_block = self.dl_queue[self.dl_queue_pos : self.dl_queue_pos + SIMULTANEOUS_DOWNLOADS]
            for episode in self.dl_block[:]:
                exe = episode.download()
                exe.on_del_event_add(self.on_download_terminated)
                self.dl_exes.append(exe)
            self.dl_queue_pos += SIMULTANEOUS_DOWNLOADS

        for episode in self.dl_queue:
            path = episode.full_path
            if os.path.exists(path):
                size = os.stat(path)[6]
            else:
                size = 0
            downloads_size += size
            total_size += episode.length

            if episode.downloaded == 1:
                counter += 1

            if counter == len(self.dl_queue) - len(self.dl_errors):
                self.hover_downloads.enable_button(0)
                self.hover_downloads.enable_button(1)
                self.hover_downloads.hide_button(2)
                self.hover_downloads_episodes.enable_button(0)
                self.hover_downloads_episodes.enable_button(1)
                self.hover_downloads_episodes.hide_button(2)

                self.dl_timer = None
                self.clear_status()
                if len(self.dl_errors) == 0:
                    self.main.notify_dlg.open('Download complete (%d)' % counter, None)
                else:
                    msg = ''
                    if counter:
                        msg += '<b>%d/%d episode(s) successfully downloaded</><br><br>' % (counter, len(self.dl_queue))
                    msg += '<b>%d episode(s) failed to download</><br>' % len(self.dl_errors)
                    for episode in self.dl_errors:
                        podcast = Podcast.load(episode.podcast_id)
                        msg += '- %s: %s<br>' % (podcast.title.encode('utf-8'), episode.title.encode('utf-8'))
                    self.main.info_dlg.open(msg)
                return False

        label = '%d/%d episode(s) (%s/%s)' % (
            counter,
            len(self.dl_queue),
            util.format_filesize(downloads_size),
            util.format_filesize(total_size),
            )
        self.set_status(label, None)
        return True

    def refresh(self, event = None, podcast_id = None):
        if not self.is_built:
            return

        if event in ('podcast-add', 'podcast-update'):
            if self.view == 'podcasts':
                self.populate_podcasts()
        elif event == 'podcast-delete':
            if self.view == 'episodes':
                if podcast_id is not None and self.current_podcast.id == podcast_id:
                    self.toggle_view()
            elif self.view == 'podcasts':
                self.populate_podcasts()
        elif event == 'episode-delete':
            if self.view == 'episodes':
                if podcast_id is not None and self.current_podcast.id == podcast_id:
                    self.populate_episodes()
            elif self.view == 'podcasts':
                self.set_title()
        else:
            if self.view == 'episodes':
                self.populate_episodes()
            elif self.view == 'podcasts':
                self.populate_podcasts()

    def toggle_ignore_status(self, btn):
        if not self.check_selected(multi_allowed = True):
            return

        for episode_id in self.selected_ids:
            episode = PodcastEpisode.load(episode_id)
            episode.toggle_ignore_status()

            if episode.downloaded == -1:
                self.list_items_icons[episode.id]['status-1'].file_set(THEME_EDJ, 'podboy/status-ignore')
                self.list_items_icons[episode.id]['status-1'].show()
            else:
                self.list_items_icons[episode.id]['status-1'].hide()
        self.set_title()


#
# Available episodes (downloaded an ready to be listen) page
#

class EpisodesPage(PodcastsPageCommon):
    def __init__(self, main):
        PodcastsPageCommon.__init__(self, main)

    def build_next(self):
        # "view episodes of selected podcast" button
        btn = elementary.Button(self.main.win)
        btn.text_set("Episodes")
        btn.size_hint_weight_set(1, 1)
        btn.size_hint_align_set(-1, -1)
        btn.callback_clicked_add(self.toggle_view)
        self.btns_actions_podcasts.append(btn)
        btn.show()

        # "more actions" hover
        self.hover_more_actions = Hover(self, 'Actions', 'podcasts')
        self.hover_more_actions.add_button('Delete All', self.confirm_delete, False)
        self.hover_more_actions.add_button('Delete Selected', self.confirm_delete, True)

        # "back to podcasts" button
        btn = elementary.Button(self.main.win)
        btn.text_set("Back")
        btn.size_hint_weight_set(1, 1)
        btn.size_hint_align_set(-1, -1)
        btn.callback_clicked_add(self.toggle_view)
        self.btns_actions_episodes.append(btn)
        btn.show()

        # "more actions" hover
        self.hover_more_actions_episodes = Hover(self, 'Actions', 'episodes')
        self.hover_more_actions_episodes.add_button('Show Details', self.show_episode_details)
        self.hover_more_actions_episodes.add_button('Toggle Played Status', self.toggle_played_status)
        self.hover_more_actions_episodes.add_button('Toggle Deletable Status', self.toggle_deletable_status)
        self.hover_more_actions_episodes.add_button('Delete All', self.confirm_delete, False)
        self.hover_more_actions_episodes.add_button('Delete Selected', self.confirm_delete, True)

        # "play selected episode" button
        btn = elementary.Button(self.main.win)
        btn.text_set("Play")
        btn.size_hint_weight_set(1, 1)
        btn.size_hint_align_set(-1, -1)
        btn.callback_clicked_add(self.play)
        self.btns_actions_episodes.append(btn)
        btn.show()

        self.toggle_view()

    def confirm_delete(self, btn, selected_items_only):
        if selected_items_only and not self.check_selected(multi_allowed = True):
            return

        if self.main.player.gst and self.main.player.gst.playing and self.main.player.episode:
            if (not selected_items_only and self.view == 'podcasts') or \
                (not selected_items_only and self.view == 'episodes' and \
                     self.current_podcast.id == self.main.player.episode.podcast_id) or \
                (selected_items_only and self.view == 'podcasts' and \
                     self.main.player.episode.podcast_id in self.selected_ids):
                self.set_status("One of episodes is playing!")
                return
            elif selected_items_only and self.view == 'episodes' and self.main.player.episode.id in self.selected_ids:
                self.set_status("This episode is playing!")
                return

        msg = None
        if self.view == 'podcasts':
            if selected_items_only:
                if len(self.selected_ids) == 1:
                    podcast = Podcast.load(self.selected_ids[0])
                    msg = 'Do you really want to delete all episodes of "%s"?' % podcast.title.encode('utf-8')
                else:
                    msg = 'Do you really want to delete all episodes of these %d podcasts?' % len(self.selected_ids)
            else:
                msg = "Do you really want to delete all episodes of all podcasts?"
        elif self.view == 'episodes':
            if selected_items_only:
                if len(self.selected_ids) == 1:
                    episode = PodcastEpisode.load(self.selected_ids[0])
                    if episode.locked:
                        self.set_status('This episode is not deletable')
                        return
                    else:
                        msg = 'Do you really want to delete the episode "%s"?' % episode.title.encode('utf-8')
                else:
                    msg = 'Do you really want to delete these %d episodes?' % len(self.selected_ids)
            else:
                msg = 'Do you really want to delete all episodes of "%s"?' % self.current_podcast.title.encode('utf-8')

        self.main.confirm_dlg.open(msg, self.delete, selected_items_only = selected_items_only)

    def delete(self, selected_items_only = True):
        ids = []
        if self.view == 'podcasts':
            if selected_items_only:
                for podcast_id in self.selected_ids:
                    for ep in db.get_episodes(podcast_id = podcast_id, downloaded = 1):
                        ids.append(ep['id'])
            else:
                for ep in db.get_episodes(downloaded = 1):
                    ids.append(ep['id'])
        elif self.view == 'episodes':
            if selected_items_only:
                ids = self.selected_ids
            else:
                for ep in db.get_episodes(podcast_id = self.current_podcast.id, downloaded = 1):
                    ids.append(ep['id'])

        counter = 0
        for episode_id in ids:
            episode = PodcastEpisode.load(episode_id)
            if not episode.locked:
                episode.delete()
                counter += 1
        if counter == 0:
            return

        # refresh list
        if self.view == 'episodes':
            if selected_items_only:
                for selected_item in self.list.selected_items_get():
                    selected_item.delete()
                self.set_title()
            else:
                self.refresh()
        else:
            self.refresh()
        # refresh list of "downloads" page
        if self.view == 'episodes':
            self.main.downloads.refresh(event = 'episode-delete', podcast_id = self.current_podcast.id)
        elif self.view == 'podcasts':
            self.main.downloads.refresh(event = 'episode-delete')

    def play(self, btn = None, episode_id = None):
        if episode_id is None and not self.check_selected():
            return
        episode_id = episode_id if episode_id else self.selected_ids[0]

        episode = PodcastEpisode.load(episode_id)
        if not episode.full_path or not os.path.exists(episode.full_path):
            self.set_status('Media not found')
            return
        if db.get_setting('bluetooth'):
            if not db.get_setting('bluetooth_device_address'):
                self.set_status('Bluetooth device not set')
                return
            if not self.main.dbus.get_bluetooth_state():
                self.set_status('Bluetooth is OFF')
                return
        self.main.show_player()
        self.main.player.play(episode)

    def refresh(self, event = None, podcast_id = None):
        if not self.is_built:
            return

        if event == 'podcast-update':
            if self.view == 'podcasts':
                self.populate_podcasts()
        elif event == 'podcast-delete':
            if self.view == 'episodes':
                if podcast_id is not None and self.current_podcast.id == podcast_id:
                    self.toggle_view()
            elif self.view == 'podcasts':
                self.populate_podcasts()
        elif event == 'episode-add':
            if self.view == 'episodes':
                if podcast_id is not None and self.current_podcast.id == podcast_id:
                    self.populate_episodes()
            elif self.view == 'podcasts':
                self.populate_podcasts()
        else:
            if self.view == 'episodes':
                self.populate_episodes()
            elif self.view == 'podcasts':
                self.populate_podcasts()

    def toggle_deletable_status(self, btn):
        if not self.check_selected(multi_allowed = True):
            return

        for episode_id in self.selected_ids:
            episode = PodcastEpisode.load(episode_id)
            episode.toggle_locked_status()

            if episode.locked == 1:
                self.list_items_icons[episode.id]['status-2'].file_set(THEME_EDJ, 'podboy/status-locked')
                self.list_items_icons[episode.id]['status-2'].show()
            else:
                self.list_items_icons[episode.id]['status-2'].hide()

    def toggle_played_status(self, btn):
        if not self.check_selected(multi_allowed = True):
            return

        for episode_id in self.selected_ids:
            episode = PodcastEpisode.load(episode_id)
            episode.toggle_played_status()

            if episode.played == 0:
                self.list_items_icons[episode.id]['status-1'].file_set(THEME_EDJ, 'podboy/status-unplayed')
                self.list_items_icons[episode.id]['status-1'].show()
            else:
                self.list_items_icons[episode.id]['status-1'].hide()


#
# Player page
#

class PlayerPage(object):
    def __init__(self, main):
        self.main = main
        self.box = None
        self.gst = None
        self.is_built = False

        self.episode = None
        self.duration = None

    def build(self):
        self.is_built = True

        self.gst = PodboyPlayer()
        self.gst.init()
        self.gst_message_timer = None

        self.box = elementary.Box(self.main.win)
        self.box.size_hint_weight_set(1, 1)
        self.box.size_hint_align_set(-1, -1)
        self.box.show()

        box = elementary.Box(self.main.win)
        box.horizontal_set(True)
        box.homogenous_set(False)
        box.size_hint_weight_set(1, 1)
        box.size_hint_align_set(-1, -1)
        self.box.pack_end(box)
        box.show()

        self.icon_cover = elementary.Icon(self.main.win)
        self.icon_cover.size_hint_align_set(0, 0)
        self.icon_cover.prescale_set(self.main.win.size[0] / 4. / elementary.scale_get())
        self.icon_cover.scale_set(0, 0)
        box.pack_end(self.icon_cover)
        self.icon_cover.callback_clicked_add(self.show_cover)
        self.icon_cover.show()

        self.scroller_details = elementary.Scroller(self.main.win)
        self.scroller_details.bounce_set(False, True)
        self.scroller_details.size_hint_weight_set(1, 1)
        self.scroller_details.size_hint_align_set(-1, -1)
        box.pack_end(self.scroller_details)
        self.scroller_details.show()

        self.entry_details = elementary.Entry(self.main.win)
        self.entry_details.line_wrap_set(True)
        self.entry_details.editable_set(False)
        self.entry_details.entry_set("Not playing")
        self.entry_details.size_hint_weight_set(1, 1)
        self.entry_details.size_hint_align_set(-1, -1)
        self.scroller_details.content_set(self.entry_details)
        self.entry_details.show()

        self.slider_position = elementary.Slider(self.main.win)
        self.slider_position.text_set('')
        self.slider_position.size_hint_weight_set(1, 0)
        self.slider_position.size_hint_align_set(-1, 0)
        self.slider_position.unit_format_set(" - / - s")
        self.slider_position.indicator_format_set("%3.0f")
        self.slider_position.min_max_set(0, 0)
        self.box.pack_end(self.slider_position)
        self.slider_position.callback_changed_add(self.on_slider_position_changed)
        self.slider_position.callback_delay_changed_add(self.set_position)
        self.slider_position.show()
        self.slider_position_timer = None
        self.slider_position_timer_lock = False

        self.slider_volume = elementary.Slider(self.main.win)
        self.slider_volume.text_set('')
        self.slider_volume.size_hint_weight_set(1, 0)
        self.slider_volume.size_hint_align_set(-1, 0)
        self.slider_volume.unit_format_set(" %3.0f" + ' / 100')
        self.slider_volume.indicator_format_set("%3.0f")
        self.slider_volume.min_max_set(0, 100)
        volume = db.get_setting('volume') or 70
        self.slider_volume.value = float(volume)
        self.box.pack_end(self.slider_volume)
        self.slider_volume.callback_delay_changed_add(self.set_volume)
        self.slider_volume.show()

        box_actions = elementary.Box(self.main.win)
        box_actions.horizontal_set(True)
        box_actions.homogenous_set(True)
        box_actions.size_hint_weight_set(1, 0)
        box_actions.size_hint_align_set(-1, -1)
        box_actions.show()
        self.box.pack_end(box_actions)

        icon_play = elementary.Icon(self.main.win)
        icon_play.file_set(THEME_EDJ, "podboy/play")
        icon_play.show()
        self.btn_play = elementary.Button(self.main.win)
        self.btn_play.icon_set(icon_play)
        self.btn_play.size_hint_weight_set(1, 1)
        self.btn_play.size_hint_align_set(-1, -1)
        self.btn_play.callback_clicked_add(self.toggle_play)
        box_actions.pack_end(self.btn_play)
        self.btn_play.show()

        icon_stop = elementary.Icon(self.main.win)
        icon_stop.file_set(THEME_EDJ, "podboy/stop")
        icon_stop.show()
        self.btn_stop = elementary.Button(self.main.win)
        self.btn_stop.icon_set(icon_stop)
        self.btn_stop.size_hint_weight_set(1, 1)
        self.btn_stop.size_hint_align_set(-1, -1)
        self.btn_stop.callback_clicked_add(self.stop)
        box_actions.pack_end(self.btn_stop)
        self.btn_stop.show()

        self.main.pager.item_simple_push(self.box)

    def on_slider_position_changed(self, *args):
        if not self.gst.playing:
            return

        self.slider_position_timer_lock = True

    def play(self, episode):
        if self.gst.playing:
            if self.episode.id == episode.id:
                # user tries to play an episode which is already running
                return
            self.stop()

        ext = os.path.basename(episode.path).split('.')[-1]
        output_type = 'bluetooth' if db.get_setting('bluetooth') and \
            db.get_setting('bluetooth_device_address') else 'normal'
        if ext.lower() == 'ogg':
            self.gst.set_pipeline(MediaTypes.OGG, output_type, db.get_setting('bluetooth_device_address'))
        elif ext.lower() == 'mp3':
            self.gst.set_pipeline(MediaTypes.MP3, output_type, db.get_setting('bluetooth_device_address'))
        else:
            return False

        self.episode = episode
        self.duration = None
        self.last_pos = episode.last_pos
        self.gst.set_file(self.episode.full_path)

        # update details
        details = '<b>[%s] %s</><br><br>%s' % (
            datetime.fromtimestamp(self.episode.updated).strftime("%Y.%m.%d"),
            self.episode.title,
            self.episode.description
            )
        self.scroller_details.region_show(0, 0, 0, 0)
        self.entry_details.entry_set(details.encode('utf-8').replace('&', '&amp;'))

        # update cover
        podcast = Podcast.load(episode.podcast_id)
        if podcast.cover_full_path and os.path.exists(podcast.cover_full_path):
            self.icon_cover.file_set(podcast.cover_full_path)
        else:
            self.icon_cover.file_set('')

        self.toggle_play()

    def poll_gst_message(self):
        bus = self.gst.get_bus()

        msg = bus.poll(gst.MESSAGE_ANY, 1)
        if msg:
            if msg.type == gst.MESSAGE_EOS:
                print 'End of episode'
                self.last_pos = 0
                self.stop()
                self.episode.update_played_status(1)
            elif msg.type == gst.MESSAGE_ERROR:
                error, debug = msg.parse_error()
                print "GStreamer bus error:", str(error), str(debug)
                print "Domain: %s, Code: %s" % (error.domain, error.code)
                print "Message:", error.message
                self.stop()
                return False
        return True

    def poll_position(self):
        if self.duration is None:
            duration = self.gst.get_duration()
            if duration > 0:
                self.duration = duration
                self.slider_position.unit_format_set(" %3.0f" + ' / %d s' % self.duration)
                self.slider_position.min_max_set(0, self.duration)
                self.episode.update_duration(duration)
            else:
                # STRANGENESS: sometime get_duration failed, just return
                # will try again next time
                return True

        if self.slider_position_timer_lock:
            return True

        position = self.gst.get_position()
        if position is None:
            return True

        self.slider_position.value = position
        if position >= self.duration:
            self.last_pos = 0
            self.stop()
            self.episode.update_played_status(1)
            return False

        self.last_pos = position
        return True

    def promote(self):
        if not self.is_built:
            self.build()
        self.main.pager.item_simple_promote(self.box)

    def set_audio_output(self):
        if self.episode:
            prev_state = self.gst.get_state()
            self.stop()
            output_type = 'bluetooth' if db.get_setting('bluetooth') and \
                db.get_setting('bluetooth_device_address') else 'normal'
            self.gst.set_pipeline(self.gst.media_type, output_type, db.get_setting('bluetooth_device_address'))
            if prev_state == gst.STATE_PLAYING:
                self.play(self.episode)

    def set_position(self, slider):
        if not self.gst.playing:
            return

        slider_position = self.slider_position.value

        try:
            self.gst.seek(slider_position)
        except Exception, e:
            print "Failed to seek to %s: %s" % (slider_position, e)

        self.slider_position_timer_lock = False

    def set_volume(self, *args):
        self.gst.set_volume(self.slider_volume.value)

    def show_cover(self, *args):
        iwin = elementary.InnerWindow(self.main.win)

        box = elementary.Box(iwin)
        iwin.content_set(box)
        box.show()

        scroller = elementary.Scroller(iwin)
        scroller.bounce_set(False, False)
        scroller.size_hint_weight_set(1, 1)
        scroller.size_hint_align_set(-1, -1)
        box.pack_end(scroller)
        scroller.show()

        cover = elementary.Image(iwin)
        cover.size_hint_weight_set(1, 1)
        cover.size_hint_align_set(-1, -1)
        podcast = Podcast.load(self.episode.podcast_id)
        if podcast.cover_full_path and os.path.exists(podcast.cover_full_path):
            cover.file_set(podcast.cover_full_path, '')
        scroller.content_set(cover)
        cover.show()

        btn = elementary.Button(iwin)
        btn.text_set('Close')
        btn.size_hint_weight_set(1, 0)
        btn.size_hint_align_set(-1, 0)
        box.pack_end(btn)
        def close(*args):
            iwin.delete()
        btn.callback_clicked_add(close)
        btn.show()

        iwin.show()

    def stop(self, *args):
        if self.gst is None:
            return

        db.save_setting('volume', int(round(self.slider_volume.value)), 'int')

        if self.gst.playing:
            self.main.dbus.set_suspend_mode(True)

            self.stop_slider_position_timer()
            self.stop_gst_message_timer()
            self.gst.stop()

            self.slider_position.value = 0
            self.btn_play.icon_get().file_set(THEME_EDJ, "podboy/play")

            self.episode.update_last_pos(self.last_pos)

    def start_gst_message_timer(self):
        if not self.gst_message_timer:
            self.gst_message_timer = ecore.timer_add(1, self.poll_gst_message)

    def start_slider_position_timer(self):
        if not self.slider_position_timer:
            self.slider_position_timer = ecore.timer_add(1, self.poll_position)

    def stop_gst_message_timer(self):
        if self.gst_message_timer:
            self.gst_message_timer.delete()
            self.gst_message_timer = None

    def stop_slider_position_timer(self):
        if self.slider_position_timer:
            self.slider_position_timer.delete()
            self.slider_position_timer = None

    def toggle_play(self, *args):
        if not self.episode:
            return

        prev_state = self.gst.get_state()
        if prev_state == gst.STATE_PLAYING:
            # FIXME: suspend mode can be set to True here
            # if device suspend and gstreamer is in PAUSE state
            # podboy stops working after resume
            self.stop_slider_position_timer()
            self.stop_gst_message_timer()
            self.gst.pause()

            self.btn_play.icon_get().file_set(THEME_EDJ, "podboy/play")
        else:
            self.main.dbus.set_suspend_mode(False)

            self.gst.play()
            self.gst.set_volume(self.slider_volume.value)
            if prev_state == gst.STATE_NULL and self.last_pos:
                # STRANGENESS: sleep 1/2 sec to help gstreamer to seek successfully
                time.sleep(.5)
                self.gst.seek(self.last_pos)
                self.slider_position.value = self.last_pos

            self.start_slider_position_timer()
            self.start_gst_message_timer()

            self.btn_play.icon_get().file_set(THEME_EDJ, "podboy/pause")


#
# Settings page
#

class SettingsPage(object):
    def __init__(self, main):
        self.main = main
        self.box = None
        self.is_built = False

    def build(self):
        self.is_built = True

        self.fullscreen = False

        self.box = elementary.Box(self.main.win)
        self.box.size_hint_weight_set(1, 1)
        self.box.size_hint_align_set(-1, -1)
        self.box.show()

        sc = elementary.Scroller(self.main.win)
        sc.bounce_set(False, False)
        sc.size_hint_weight_set(1, 1)
        sc.size_hint_align_set(-1, -1)
        self.box.pack_end(sc)
        sc.show()

        box = elementary.Box(self.main.win)
        box.size_hint_weight_set(1, 0)
        box.size_hint_align_set(-1, 0)
        sc.content_set(box)
        box.show()

        #
        # Media directory
        #
        frame = elementary.Frame(self.main.win)
        frame.text_set("Media Directory")
        frame.size_hint_align_set(-1, -1)
        box.pack_end(frame)
        frame.show()

        self.btn_media_dir = elementary.Button(self.main.win)
        self.btn_media_dir.text_set((db.get_setting('media_dir') or 'Not yet defined').encode('utf-8'))
        self.btn_media_dir.callback_clicked_add(self.select_media_dir)
        frame.content_set(self.btn_media_dir)
        self.btn_media_dir.show()

        #
        # Clean-up: auto delete old episodes on startup
        #
        frame = elementary.Frame(self.main.win)
        frame.text_set("Episodes Auto Cleanup")
        frame.size_hint_align_set(-1, -1)
        box.pack_end(frame)
        frame.show()

        box2 = elementary.Box(self.main.win)
        frame.content_set(box2)
        box2.show()

        episodes_old_auto_delete = db.get_setting('episodes_old_auto_delete') or False
        toggle = elementary.Check(self.main.win)
        toggle.style_set("toggle")
        toggle.text_set("Delete Played")
        toggle.size_hint_align_set(-1, 0)
        toggle.state_set(episodes_old_auto_delete)
        toggle.text_part_set("on", "Yes")
        toggle.text_part_set("off", "No")
        toggle.callback_changed_add(self.toggle_episodes_old_auto_delete)
        box2.pack_end(toggle)
        toggle.show()

        box3 = elementary.Box(self.main.win)
        box3.size_hint_align_set(-1, 0)
        box3.horizontal_set(True)
        box2.pack_end(box3)
        box3.show()

        label = elementary.Label(self.main.win)
        label.size_hint_weight_set(1, 0.5)
        label.size_hint_align_set(-1, 0.5)
        label.text_set(' On Startup After')
        box3.pack_end(label)
        label.show()

        self.sp_ep_old_age = elementary.Spinner(self.main.win)
        self.sp_ep_old_age.size_hint_weight_set(1, 1)
        self.sp_ep_old_age.size_hint_align_set(-1, -1)
        self.sp_ep_old_age.min_max_set(1, 365)
        self.sp_ep_old_age.value_set(db.get_setting('episodes_old_age') or 7)
        self.sp_ep_old_age.label_format_set('%.0f days')
        self.sp_ep_old_age.callback_delay_changed_add(self.set_episodes_old_age)
        box3.pack_end(self.sp_ep_old_age)
        self.sp_ep_old_age.show()
        self.sp_ep_old_age.disabled_set(not episodes_old_auto_delete)

        #
        # Bluetooth settings
        #
        frame = elementary.Frame(self.main.win)
        frame.text_set("Bluetooth Settings")
        frame.size_hint_weight_set(1, 1)
        frame.size_hint_align_set(-1, -1)
        box.pack_end(frame)
        frame.show()

        box_bt = elementary.Box(self.main.win)
        box_bt.size_hint_weight_set(1, 0)
        box_bt.size_hint_align_set(-1, 0)
        frame.content_set(box_bt)
        box_bt.show()

        # bluetooth audio output
        ic = elementary.Icon(self.main.win)
        ic.file_set(THEME_EDJ, "podboy/bluetooth")
        toggle = elementary.Check(self.main.win)
        toggle.style_set("toggle")
        toggle.icon_set(ic)
        toggle.text_set("Audio Output")
        toggle.size_hint_align_set(-1, 0)
        toggle.state_set(db.get_setting('bluetooth') or False)
        toggle.text_part_set("off", "No")
        toggle.text_part_set("on", "Yes")
        toggle.callback_changed_add(self.toggle_bluetooth)
        box_bt.pack_end(toggle)
        toggle.show()

        # device address
        frame = elementary.Frame(self.main.win)
        frame.text_set("Device Address")
        frame.size_hint_weight_set(1, 1)
        frame.size_hint_align_set(-1, -1)
        box_bt.pack_end(frame)
        frame.show()

        box_bt_addr = elementary.Box(self.main.win)
        box_bt_addr.horizontal_set(True)
        box_bt_addr.size_hint_weight_set(1, 0)
        box_bt_addr.size_hint_align_set(-1, 0)
        frame.content_set(box_bt_addr)
        box_bt_addr.show()

        self.label_bt_addr = elementary.Label(self.main.win)
        self.label_bt_addr.text_set(db.get_setting('bluetooth_device_address') or 'Not Yet Defined')
        self.label_bt_addr.size_hint_weight_set(1, 1)
        self.label_bt_addr.size_hint_align_set(-1, .5)
        box_bt_addr.pack_end(self.label_bt_addr)
        self.label_bt_addr.show()

        btn_bt_addr = elementary.Button(self.main.win)
        btn_bt_addr.text_set('Set')
        btn_bt_addr.size_hint_weight_set(1, 0)
        btn_bt_addr.size_hint_align_set(-1, 0)
        btn_bt_addr.callback_clicked_add(self.set_bt_addr)
        box_bt_addr.pack_end(btn_bt_addr)
        btn_bt_addr.show()

        #
        # Display
        #
        frame = elementary.Frame(self.main.win)
        frame.text_set("Display")
        frame.size_hint_weight_set(1, 1)
        frame.size_hint_align_set(-1, -1)
        box.pack_end(frame)
        frame.show()

        box_display = elementary.Box(self.main.win)
        box_display.size_hint_weight_set(1, 0)
        box_display.size_hint_align_set(-1, 0)
        frame.content_set(box_display)
        box_display.show()

        # fullscreen
        icon = elementary.Icon(self.main.win)
        icon.file_set(THEME_EDJ, "podboy/fullscreen")
        toggle = elementary.Check(self.main.win)
        toggle.icon_set(icon)
        toggle.style_set("toggle")
        toggle.text_set("Fullscreen")
        toggle.size_hint_align_set(-1, 0)
        toggle.state_set(self.fullscreen)
        toggle.text_part_set("off", "No")                                       
        toggle.text_part_set("on", "Yes")
        toggle.callback_changed_add(self.toggle_fullscreen)
        box_display.pack_end(toggle)
        toggle.show()
        icon.show()

        # orientation
        icon = elementary.Icon(self.main.win)
        icon.file_set(THEME_EDJ, "podboy/orientation")
        toggle = elementary.Check(self.main.win)
        toggle.icon_set(icon)
        toggle.style_set("toggle")
        toggle.text_set("Orientation")
        toggle.size_hint_align_set(-1, 0)
        toggle.state_set(db.get_setting('display_orientation') or False)
        toggle.text_part_set("off", "Portrait")                                       
        toggle.text_part_set("on", "Landscape")
        toggle.callback_changed_add(self.toggle_orientation)
        box_display.pack_end(toggle)
        toggle.show()
        icon.show()

        # finger size
        slider = elementary.Slider(self.main.win)
        slider.text_set('Finger Size ')
        slider.size_hint_weight_set(1, 0)
        slider.size_hint_align_set(-1, 0)
        slider.unit_format_set(" %3.0f px")
        slider.indicator_format_set("%3.0f")
        slider.min_max_set(40, 100)
        finger_size = db.get_setting('display_finger_size') or elementary.config_finger_size_get()
        slider.value = finger_size
        box_display.pack_end(slider)
        slider.callback_delay_changed_add(self.set_finger_size)
        slider.show()

        # scaling factor
        slider = elementary.Slider(self.main.win)
        slider.text_set('Scaling Factor ')
        slider.size_hint_weight_set(1, 0)
        slider.size_hint_align_set(-1, 0)
        slider.unit_format_set(" %3.1f")
        slider.indicator_format_set("%3.1f")
        slider.min_max_set(1, 2)
        scale = db.get_setting('display_scale') or elementary.scale_get()
        slider.value = scale
        box_display.pack_end(slider)
        slider.callback_delay_changed_add(self.set_scale)
        slider.show()

        #
        # Interface
        #
        frame = elementary.Frame(self.main.win)
        frame.text_set("Interface")
        frame.size_hint_weight_set(1, 1)
        frame.size_hint_align_set(-1, -1)
        box.pack_end(frame)
        frame.show()

        box_interface = elementary.Box(self.main.win)
        box_interface.size_hint_weight_set(1, 0)
        box_interface.size_hint_align_set(-1, 0)
        frame.content_set(box_interface)
        box_interface.show()

        # multi select in list
        toggle = elementary.Check(self.main.win)
        toggle.text_set("Multi Select In List")
        toggle.style_set("toggle")
        toggle.size_hint_align_set(-1, 0)
        toggle.state_set(db.get_setting('gui_list_multi_select') or False)
        toggle.text_part_set("off", "No")                                       
        toggle.text_part_set("on", "Yes")
        toggle.callback_changed_add(self.toggle_list_multi_select)
        box_interface.pack_end(toggle)
        toggle.show()

        self.main.pager.item_simple_push(self.box)

    def promote(self):
        if not self.is_built:
            self.build()
        self.main.pager.item_simple_promote(self.box)

    def select_media_dir(self, btn):
        self.main.fs_dlg.open('Select Media Folder', self.set_media_dir)

    def set_bt_addr(self, *args, **kwargs):
        iwin = elementary.InnerWindow(self.main.win)

        box = elementary.Box(iwin)
        iwin.content_set(box)
        box.show()

        frame = elementary.Frame(iwin)
        frame.text_set('Select Bluetooth Device')
        frame.size_hint_weight_set(1, 1)
        frame.size_hint_align_set(-1, -1)
        box.pack_start(frame)
        frame.show()

        list_devices = elementary.List(self.main.win)
        list_devices.mode = elementary.ELM_LIST_COMPRESS
        frame.content_set(list_devices)
        list_devices.show()

        label_status = elementary.Label(iwin)
        label_status.text_set('')
        label_status.size_hint_weight_set(0, 0)
        label_status.size_hint_align_set(0.5, 0.5)
        box.pack_end(label_status)
        label_status.show()

        box_actions = elementary.Box(iwin)
        box_actions.size_hint_weight_set(1, 0)
        box_actions.size_hint_align_set(-1, 0)
        box_actions.horizontal_set(True)
        box_actions.homogenous_set(True)
        box.pack_end(box_actions)
        box_actions.show()
 
        btn = elementary.Button(iwin)
        btn.text_set('Cancel')
        btn.size_hint_weight_set(1, 0)
        btn.size_hint_align_set(-1, 0)
        box_actions.pack_end(btn)
        btn.show()
        def close(*args):
            iwin.delete()
        btn.callback_clicked_add(close)

        btn = elementary.Button(iwin)
        btn.text_set('OK')
        btn.size_hint_weight_set(1, 0)
        btn.size_hint_align_set(-1, 0)
        box_actions.pack_end(btn)
        btn.show()
        def save(*args):
            if list_devices.selected_item_get():
                addr = list_devices.selected_item_get().data_get()[0][0]
                db.save_setting('bluetooth_device_address', addr)
                self.label_bt_addr.text_set(addr)
                close()
        btn.callback_clicked_add(save)

        devices = self.main.dbus.list_bluetooth_devices()
        if devices:
            for device in devices:
                list_devices.item_append('%(name)s - %(addr)s' % device, None, None, None, device['addr'])
            list_devices.go()
        else:
            label_status.text_set('Failed to list Bluetooth devices')

        iwin.activate()

    def set_episodes_old_age(self, spinner):
        db.save_setting('episodes_old_age', int(spinner.value_get()), 'int')

    def set_finger_size(self, slider):
        value = int(round(slider.value))
        elementary.config_finger_size_set(value)
        db.save_setting('display_finger_size', value, 'int')

    def set_media_dir(self, path):
        if not path:
            return

        db.save_setting('media_dir', path)
        self.btn_media_dir.text_set(path)

    def set_scale(self, slider):
        value = float('%.1f' % slider.value)
        elementary.scale_set(value)
        db.save_setting('display_scale', value, 'float')

    def toggle_bluetooth(self, toggle):
        prev_state = db.get_setting('bluetooth')
        new_state = toggle.state_get()
        if prev_state is None or new_state != prev_state:
            db.save_setting('bluetooth', new_state, 'bool')
            self.main.player.set_audio_output()

    def toggle_episodes_old_auto_delete(self, toggle):
        db.save_setting('episodes_old_auto_delete', toggle.state_get(), 'bool')
        self.sp_ep_old_age.disabled_set(not toggle.state_get())
        if db.get_setting('episodes_old_age') is None:
            db.save_setting('episodes_old_age', 7, 'int')

    def toggle_fullscreen(self, toggle):
        state = toggle.state_get()
        if state != self.fullscreen:
            self.main.win.fullscreen_set(state)
            self.fullscreen = state

    def toggle_list_multi_select(self, toggle):
        state = toggle.state_get()
        for page in (self.main.podcasts, self.main.downloads, self.main.episodes):
            if page.is_built:
                page.list.multi_select_set(state)
        db.save_setting('gui_list_multi_select', state, 'bool')

    def toggle_orientation(self, toggle):
        self.main.win.override_set(True)
        self.main.win.rotation_set(toggle.state_get() * 270)
        self.main.win.override_set(False)
        db.save_setting('display_orientation', toggle.state_get(), 'bool')

#
# About page
#

class AboutPage(object):
    def __init__(self, main):
        self.main = main
        self.box = None
        self.is_built = False

    def build(self):
        self.is_built = True

        self.box = elementary.Box(self.main.win)
        self.box.size_hint_align_set(-1, -1)
        self.box.show()

        label = elementary.Label(self.main.win)
        label.text_set('<b>%s %s</>' % (APP_NAME, APP_VERSION))
        label.size_hint_align_set(0.5, 0.5)
        self.box.pack_end(label)
        label.show()

        scroller = elementary.Scroller(self.main.win)
        scroller.bounce_set(False, True)
        scroller.size_hint_weight_set(1, 1)
        scroller.size_hint_align_set(-1, -1)
        self.box.pack_end(scroller)
        scroller.show()

        about  = """\
A podcast aggregator / player

<b>Copyright</> © 2009-2012 Valéry (aka valos) Febvre

<b>Licensed</> under the GNU GPL v3.

<b>Homepage</> http://code.google.com/p/podboy/

If you like this application, send me an email to <b>vfebvre@easter-eggs.com</>
or buy me a beer (using link available on homepage).

Happy listening
"""

        entry = elementary.Entry(self.main.win)
        entry.editable_set(False)
        entry.line_wrap_set(True)
        entry.size_hint_weight_set(1, 1)
        entry.size_hint_align_set(-1, -1)
        entry.entry_set(about.replace('\n', '<br>'))
        scroller.content_set(entry)
        entry.show()

        self.main.pager.item_simple_push(self.box)

    def promote(self):
        if not self.is_built:
            self.build()
        self.main.pager.item_simple_promote(self.box)


#
# Widgets
#

class Hover(object):
    def __init__(self, parent, label, view):
        self.main = parent.main
        self.buttons = []

        self.obj = elementary.Hover(self.main.win)
        self.obj.style_set("popout")
        self.obj.parent_set(self.main.win)

        self.box = elementary.Box(self.main.win)
        self.obj.content_set("top", self.box)

        # middle button
        btn = elementary.Button(self.main.win)
        btn.text_set(label)
        btn.callback_clicked_add(self._close)
        self.obj.content_set("middle", btn)

        # bouton
        btn = elementary.Button(self.main.win)
        btn.text_set(label)
        btn.size_hint_weight_set(1, 1)
        btn.size_hint_align_set(-1, -1)
        btn.callback_clicked_add(self._open)
        btn.show()
        self.obj.target_set(btn)

        if view == 'podcasts':
            parent.btns_actions_podcasts.append(btn)
        elif view == 'episodes':
            parent.btns_actions_episodes.append(btn)

    def _close(self, *args):
        self.obj.hide()

    def _open(self, *args):
        self.obj.show()

    def _set_callback_clicked(self, btn = None, func = None, *args):
        self._close()
        func(btn, *args)

    def add_button(self, label = '', callback_clicked_func = None, *args):
        btn = elementary.Button(self.main.win)
        btn.text_set(label)
        btn.size_hint_weight_set(1, 1)
        btn.size_hint_align_set(-1, -1)
        btn.callback_clicked_add(self._set_callback_clicked, callback_clicked_func, *args)
        self.box.pack_end(btn)
        self.buttons.append(btn)
        btn.show()

    def disable_button(self, pos):
        self.buttons[pos].disabled_set(True)

    def enable_button(self, pos):
        self.buttons[pos].disabled_set(False)

    def hide_button(self, pos):
        btn = self.buttons[pos]
        self.box.unpack(btn)
        btn.hide()

    def show_button(self, pos):
        btn = self.buttons[pos]
        self.box.pack_end(btn)
        btn.show()


#
# Dialogs
#

class ConfirmDialog(object):
    def __init__(self, main, title = 'Confirm ?'):
        self.main = main
        self.win = None
        self.title = title

    def _close(self, *args):
        self.win.hide()

    def _confirm(self, *args):
        self._close()
        self.confirm_cb(**self.confirm_cb_args)

    def _create(self):
        self.win = elementary.InnerWindow(self.main.win)
        self.win.style_set("minimal_vertical")

        box = elementary.Box(self.win)
        self.win.content_set(box)
        box.show()

        frame = elementary.Frame(self.win)
        frame.text_set(self.title)
        frame.size_hint_weight_set(1, 1)
        frame.size_hint_align_set(-1, -1)
        box.pack_start(frame)
        frame.show()

        self.entry_title = elementary.Entry(self.win)
        self.entry_title.line_wrap_set(True)
        self.entry_title.editable_set(False)
        self.entry_title.entry_set('')
        self.entry_title.size_hint_weight_set(1, 1)
        frame.content_set(self.entry_title)
        self.entry_title.show()

        box_actions = elementary.Box(self.win)
        box_actions.horizontal_set(True)
        box_actions.homogenous_set(True)
        box_actions.size_hint_weight_set(1, 0)
        box_actions.size_hint_align_set(-1, -1)
        box.pack_end(box_actions)
        box_actions.show()

        btn_no = elementary.Button(self.win)
        btn_no.text_set("No")
        btn_no.size_hint_weight_set(1, 1)
        btn_no.size_hint_align_set(-1, -1)
        btn_no.callback_clicked_add(self._close)
        box_actions.pack_end(btn_no)
        btn_no.show()

        btn_yes = elementary.Button(self.win)
        btn_yes.text_set("Yes")
        btn_yes.size_hint_weight_set(1, 1)
        btn_yes.size_hint_align_set(-1, -1)
        btn_yes.callback_clicked_add(self._confirm)
        box_actions.pack_end(btn_yes)
        btn_yes.show()

    def open(self, message, confirm_cb, **kwargs):
        if self.win is None:
            self._create()

        self.entry_title.entry_set(message)
        self.confirm_cb = confirm_cb
        self.confirm_cb_args = kwargs
        self.win.show()


class FileselectorDialog(object):
    def __init__(self):
        self.win = None

    def _create(self):
        self.win = elementary.Window("fileselector", elementary.ELM_WIN_BASIC)

        bg = elementary.Background(self.win)
        self.win.resize_object_add(bg)
        bg.size_hint_weight_set(1, 1)
        bg.show()

        vbox = elementary.Box(self.win)
        self.win.resize_object_add(vbox)
        vbox.size_hint_weight_set(1, 1)
        vbox.show()

        fs = elementary.Fileselector(self.win)
        fs.expandable_set(False)
        fs.path_set(os.getenv("HOME"))
        fs.size_hint_weight_set(1, 1)
        fs.size_hint_align_set(-1, -1)
        fs.callback_done_add(self._done)
        vbox.pack_end(fs)
        fs.show()

        self.win.resize(480, 640)
        self.win.show()

    def _done(self, fs, selected):
        self.callback_done(selected)
        self.win.hide()
        self.win.evas_get().render()

    def open(self, title, callback_done):
        if self.win is None:
            self._create()

        self.win.title_set(title)
        self.callback_done = callback_done
        self.win.show()


class InfoDialog(object):
    def __init__(self, main):
        self.main = main
        self.win = None

    def _close(self, *args):
        self.win.hide()
        self.scroller.region_show(0, 0, 0, 0)

    def _create(self):
        self.win = elementary.InnerWindow(self.main.win)

        box = elementary.Box(self.win)
        self.win.content_set(box)
        box.show()

        self.scroller = elementary.Scroller(self.win)
        self.scroller.bounce_set(False, True)
        self.scroller.size_hint_weight_set(1, 1)
        self.scroller.size_hint_align_set(-1, -1)
        box.pack_end(self.scroller)
        self.scroller.show()

        self.entry = elementary.Entry(self.win)
        self.entry.line_wrap_set(True)
        self.entry.editable_set(False)
        self.entry.entry_set('')
        self.entry.size_hint_weight_set(1, 1)
        self.scroller.content_set(self.entry)
        self.entry.show()

        btn = elementary.Button(self.win)
        btn.text_set("Close")
        btn.size_hint_weight_set(1, 0)
        btn.size_hint_align_set(-1, -1)
        btn.callback_clicked_add(self._close)
        box.pack_end(btn)
        btn.show()

    def open(self, info):
        if self.win is None:
            self._create()

        self.entry.entry_set(info)
        self.win.show()


class NotifyDialog(object):
    def __init__(self, main):
        self.main = main
        self.notify = None

    def _close(self, btn):
        self.notify.hide()

    def _create(self):
        self.notify = elementary.Notify(self.main.win)
        self.notify.repeat_events_set(False)
        self.notify.orient_set(elementary.ELM_NOTIFY_ORIENT_LEFT)

        box = elementary.Box(self.main.win)
        box.size_hint_weight_set(1, 1)
        box.horizontal_set(True)
        self.notify.content_set(box)
        box.show()

        self.label = elementary.Label(self.main.win)
        box.pack_end(self.label)
        self.label.show()

        btn = elementary.Button(self.main.win)
        btn.text_set("Close")
        btn.callback_clicked_add(self._close)
        box.pack_end(btn)
        btn.show()

    def open(self, message, timeout = STATUS_MSG_TIMEOUT):
        if self.notify is None:
            self._create()

        self.label.text_set(message)
        self.notify.timeout_set(timeout if timeout is not None else 0)
        self.notify.show()
